#include <iostream>
using namespace std;
#include <cstring>
#include <queue>
#include <algorithm>

/*********************** Prim ************************/
// 从未确定的集合中拿出距离已确定集合最近的点，加入到集合中
/*
const int N = 510, INF = 0x3f3f3f3f;
int g[N][N];
int dist[N]; // 到集合的距离
bool view[N];
int n, m;

int Prim()
{
    int sum = 0;
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for(int i = 0; i < n; ++i)
    {
        //找距离集合最近的点
        int t = -1;
        for(int j = 1; j <= n; ++j)
        {
            if(!view[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        }
        if(dist[t] == INF) return INF; //没找到，不连通

        //加入到集合，更新其邻接点的距离
        view[t] = true;
        sum += dist[t];
        for(int j = 1; j <= n; ++j)
        {
            if(g[t][j] < dist[j])
                dist[j] = g[t][j];
        }
    }
    return sum;
}
int main()
{
    memset(g, 0x3f, sizeof g);
    scanf("%d %d", &n, &m);
    int a, b, c;
    while(m--)
    {
        scanf("%d %d %d", &a, &b, &c);
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    int ans = Prim();
    if(ans ==  INF) puts("impossible");
    else    printf("%d", ans);
    return 0;
}*/

/************************ Kruskal ************************/
// 从所有的边中拿出权值最小的，判断是否会构成环，不构成环就加入到边的集合
/*
const int M = 2e5 + 10, N = 1e5 + 10, INF = 0x3f3f3f3f;
struct Edge
{
    int a, b, w;
    bool operator<(const Edge &e) const
    {
        return w < e.w;
    }
} edges[M];

int n, m;
int p[N]; // 并查集. p[x] < 0，则x为根，-p[x]存的是x集合总结点
int find(int x)
{
    if (p[x] < 0)
        return x;
    return p[x] = find(p[x]);
}
int Kruskal()
{
    memset(p, -1, sizeof p);
    int sum = 0;
    sort(edges, edges + m);
    for (int i = 0; i < m; ++i)
    {
        auto [a, b, w] = edges[i];
        int x = find(a), y = find(b);
        if (x == y)
            continue;
        p[x] += p[y];
        p[y] = x;
        sum += w;
    }
    int i = 1; // 1 ~ n, 而非0 ~ n-1
    for (; i <= n; ++i)
    {
        if (p[i] < 0)
            break;
    }
    if (-p[i] != n)
        return INF;
    return sum;
}
int main()
{
    scanf("%d %d", &n, &m);
    int a, b, c;
    for (int i = 0; i < m; ++i)
    {
        scanf("%d %d %d", &a, &b, &c);
        edges[i] = {a, b, c};
    }
    int ans = Kruskal();
    if (ans == INF)
        puts("impossible");
    else
        printf("%d", ans);
    return 0;
}*/

/******************* 染色法判定二分图 *******************/
// 节点由两个集合组成，且两个集合内部没有边的图。二分图不存在长度为奇数的环
// dfs / bfs
/*
const int N = 1e5 + 10, M = 2e5 + 10;
int h[N], e[M], ne[M], idx;
int color[N]; //0表示未染色，1 白色，2 黑色
int n, m;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

bool dfs(int i, int c)
{
    color[i] = c;
    for(int j = h[i]; j != -1; j = ne[j])
    {
        int u = e[j];
        if(!color[u])
        { // 没有被染色，直接dfs
            if(!dfs(u, 3 - c))
                return false;
        }
        else
        { // 被染色了，判断 i 跟 u 颜色是否相同
            if(color[u] == c)
                return false;
        }
    }
    return true;
}

// //bfs1：按层扩展
// bool bfs(int i)
// {
//     queue<int> q;
//     q.push(i);
//     color[i] = 1;
//     int flag_c = 2; //下一层需要染的颜色
//     while(q.size())
//     {
//         int sz = q.size();
//         while(sz--)
//         {
//             int f = q.front(); q.pop();
//             //遍历 f 的邻接点
//             for(int i = h[f]; i != -1; i = ne[i])
//             {
//                 // f的邻接点 e[i]未染色，那就染色后入队
//                 if(!color[e[i]])
//                 {
//                     color[e[i]] = flag_c;
//                     q.push(e[i]);
//                 }
//                 else
//                 { // f 的邻接点 e[i] 已染色，判断是否跟 f 的颜色相同
//                     if(color[e[i]] == color[f])
//                         return false;
//                 }
//             }
//         }
//         // 为什么这样可以呢？因为 while(sz--) 是一层一层遍历
//         if(flag_c == 2) flag_c = 1;
//         else    flag_c = 2;
//     }
//     return true;
// }

// //bfs2：按点扩展
int q[N];
bool bfs(int i)
{
    int hh = 0, tt = 0;
    q[tt++] = i;
    color[i] = 1;
    while(hh != tt)
    {
        int f = q[hh++];
        for(int i = h[f]; i != -1; i = ne[i])
        {
            int u = e[i];
            if(!color[u])
            {
                color[u] = 3 - color[f];
                q[tt++] = u;
            }
            else
            {
                if(color[u] == color[f])
                    return false;
            }
        }
    }
    return true;
}
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d %d", &n, &m);
    int a, b;
    while(m--)
    {
        scanf("%d %d", &a, &b);
        add(a, b);
        add(b, a);
    }
    for (int i = 1; i <= n; ++i)
    {
        if (!color[i])
        {
            if (!bfs(i))
            {
                puts("No");
                return 0;
            }
        }
    }
    puts("Yes");
    return 0;
}*/

/**************** 二分图的最大匹配(匈牙利算法) **************/
// https://www.acwing.com/problem/content/description/863/

const int N = 510, M = 1e5 + 10;
int h[N], e[M], ne[M], idx;
int match[N]; // match[j] = i; 女生j已匹配男生i
int n1, n2, m;
bool st[N]; // st 标记是否递归找过

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool find(int u)
{
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true;
            // 当前尝试点没有被匹配或者和当前尝试点匹配的那个点可以换另一个匹配
            if (!match[j] || find(match[j]))
            {
                match[j] = u;
                return true;
            }
        }
    }
    return false;
}
int ans;
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d %d %d", &n1, &n2, &m);
    int a, b;
    while (m--)
    {
        scanf("%d %d", &a, &b);
        add(a, b);
    }

    for (int i = 1; i <= n1; ++i)
    {
        memset(st, 0, sizeof st);
        if (find(i))
            ans++;
    }
    printf("%d", ans);
    return 0;
}
/*
4 4 7
1 1
1 2
2 2
2 3
3 1
3 2
4 3
*/

/*作业：
372 棋盘覆盖
*/